#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"

namespace log_ns
{

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    class LogMessage
    {
    public:
        std::string _level;
        pid_t _id;
        std::string _filename;
        int _filenumber;
        std::string _curr_time;
        std::string _message_info;
    };

    const std::string logfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    class log
    {
    private:
        void FlushLogToScreen(LogMessage &log)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                   log._level.c_str(),
                   log._id,
                   log._filename.c_str(),
                   log._filenumber,
                   log._curr_time.c_str(),
                   log._message_info.c_str());
        }
        void FlushLogToFile(LogMessage &log)
        {
            std::ofstream out(logfile, std::ios::app);
            char logtxt[1024];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
                     log._level.c_str(),
                     log._id,
                     log._filename.c_str(),
                     log._filenumber,
                     log._curr_time.c_str(),
                     log._message_info.c_str());
            out.write(logtxt, strlen(logtxt));
            out.close();
        }
        void FlushLog(LogMessage &log)
        {
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushLogToScreen(log);
                break;
            case FILE_TYPE:
                FlushLogToFile(log);
                break;
            }
        }
        std::string LevelToString(int level)
        {
            switch (level)
            {
            case DEBUG:
                return "DEBUG";
            case INFO:
                return "INFO";
            case WARNING:
                return "WARNING";
            case ERROR:
                return "ERROR";
            case FATAL:
                return "FATAL";
            default:
                return "UNKNOW";
            }
        }
        std::string GetCurTime()
        {
            time_t now = time(nullptr);
            struct tm *curtime = localtime(&now);
            char buffer[1024];
            snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                     curtime->tm_year + 1900,
                     curtime->tm_mon + 1,
                     curtime->tm_mday,
                     curtime->tm_hour,
                     curtime->tm_min,
                     curtime->tm_sec);
            return buffer;
        }

    public:
        log(const std::string &filename = logfile) : _filename(filename), _type(SCREEN_TYPE)
        {
        }
        ~log()
        {
        }
        void Enable(int type)
        {
            _type = type;
        }
        void logMessage(const std::string &filename, int filenumber, int level, const char *format, ...)
        {
            LogMessage log;

            log._filename = filename;
            log._filenumber = filenumber;
            log._id = getpid();
            log._level = LevelToString(level);
            log._curr_time = GetCurTime();

            va_list ap;
            va_start(ap, format);
            char info[1024];
            vsnprintf(info, sizeof(info), format, ap);
            va_end(ap);

            log._message_info = info;
            FlushLog(log);
        }

    private:
        int _type;
        std::string _filename;
    };

    log_ns::log lg;

#define LOG(Level, Format, ...)                                          \
    do                                                                   \
    {                                                                    \
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
#define EnableFILE()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)
};